/**
 * TAC AgentWare
 * http://www.sics.se/tac        tac-dev@sics.se
 *
 * Copyright (c) 2001-2005 SICS AB. All rights reserved.
 *
 * SICS grants you the right to use, modify, and redistribute this
 * software for noncommercial purposes, on the conditions that you:
 * (1) retain the original headers, including the copyright notice and
 * this text, (2) clearly document the difference between any derived
 * software and the original, and (3) acknowledge your use of this
 * software in pertaining publications and reports.  SICS provides
 * this software "as is", without any warranty of any kind.  IN NO
 * EVENT SHALL SICS BE LIABLE FOR ANY DIRECT, SPECIAL OR INDIRECT,
 * PUNITIVE, INCIDENTAL OR CONSEQUENTIAL LOSSES OR DAMAGES ARISING OUT
 * OF THE USE OF THE SOFTWARE.
 *
 * -----------------------------------------------------------------
 *
 * Author  : Joakim Eriksson, Niclas Finne, Sverker Janson
 * Created : 23 April, 2002
 * Updated : $Date: 2005/06/07 19:06:16 $
 *	     $Revision: 1.1 $
 * ---------------------------------------------------------
 * DummyAgent is a simplest possible agent for TAC. It uses
 * the TACAgent agent ware to interact with the TAC server.
 *
 * Important methods in TACAgent:
 *
 * Retrieving information about the current Game
 * ---------------------------------------------
 * int getGameID()
 *  - returns the id of current game or -1 if no game is currently plaing
 *
 * getServerTime()
 *  - returns the current server time in milliseconds
 *
 * getGameTime()
 *  - returns the time from start of game in milliseconds
 *
 * getGameTimeLeft()
 *  - returns the time left in the game in milliseconds
 *
 * getGameLength()
 *  - returns the game length in milliseconds
 *
 * int getAuctionNo()
 *  - returns the number of auctions in TAC
 *
 * int getClientPreference(int client, int type)
 *  - returns the clients preference for the specified type
 *   (types are TACAgent.{ARRIVAL, DEPARTURE, HOTEL_VALUE, E1, E2, E3}
 *
 * int getAuctionFor(int category, int type, int day)
 *  - returns the auction-id for the requested resource
 *   (categories are TACAgent.{CAT_FLIGHT, CAT_HOTEL, CAT_ENTERTAINMENT
 *    and types are TACAgent.TYPE_INFLIGHT, TACAgent.TYPE_OUTFLIGHT, etc)
 *
 * int getAuctionCategory(int auction)
 *  - returns the category for this auction (CAT_FLIGHT, CAT_HOTEL,
 *    CAT_ENTERTAINMENT)
 *
 * int getAuctionDay(int auction)
 *  - returns the day for this auction.
 *
 * int getAuctionType(int auction)
 *  - returns the type for this auction (TYPE_INFLIGHT, TYPE_OUTFLIGHT, etc).
 *
 * int getOwn(int auction)
 *  - returns the number of items that the agent own for this
 *    auction
 *
 * Submitting Bids
 * ---------------------------------------------
 * void submitBid(Bid)
 *  - submits a bid to the tac server
 *
 * void replaceBid(OldBid, Bid)
 *  - replaces the old bid (the current active bid) in the tac server
 *
 *   Bids have the following important methods:
 *    - create a bid with new Bid(AuctionID)
 *
 *   void addBidPoint(int quantity, float price)
 *    - adds a bid point in the bid
 *
 * Help methods for remembering what to buy for each auction:
 * ----------------------------------------------------------
 * int getAllocation(int auctionID)
 *   - returns the allocation set for this auction
 * void setAllocation(int auctionID, int quantity)
 *   - set the allocation for this auction
 *
 *
 * Callbacks from the TACAgent (caused via interaction with server)
 *
 * bidUpdated(Bid bid)
 *  - there are TACAgent have received an answer on a bid query/submission
 *   (new information about the bid is available)
 * bidRejected(Bid bid)
 *  - the bid has been rejected (reason is bid.getRejectReason())
 * bidError(Bid bid, int error)
 *  - the bid contained errors (error represent error status - commandStatus)
 *
 * quoteUpdated(Quote quote)
 *  - new information about the quotes on the auction (quote.getAuction())
 *    has arrived
 * quoteUpdated(int category)
 *  - new information about the quotes on all auctions for the auction
 *    category has arrived (quotes for a specific type of auctions are
 *    often requested at once).

 * auctionClosed(int auction)
 *  - the auction with id "auction" has closed
 *
 * transaction(Transaction transaction)
 *  - there has been a transaction
 *
 * gameStarted()
 *  - a TAC game has started, and all information about the
 *    game is available (preferences etc).
 *
 * gameStopped()
 *  - the current game has ended
 *
 */

package se.sics.tac.aw;

import static se.sics.tac.aw.TACAgent.ARRIVAL;
import static se.sics.tac.aw.TACAgent.DEPARTURE;
import static se.sics.tac.aw.TACAgent.E1;
import static se.sics.tac.aw.TACAgent.E2;
import static se.sics.tac.aw.TACAgent.E3;
import static se.sics.tac.aw.TACAgent.HOTEL_VALUE;


import static se.sics.tac.aw.TACAgent.CAT_ENTERTAINMENT;
import static se.sics.tac.aw.TACAgent.CAT_FLIGHT;
import static se.sics.tac.aw.TACAgent.CAT_HOTEL;

import static se.sics.tac.aw.TACAgent.TYPE_CHEAP_HOTEL;
import static se.sics.tac.aw.TACAgent.TYPE_GOOD_HOTEL;
import static se.sics.tac.aw.TACAgent.TYPE_INFLIGHT;
import static se.sics.tac.aw.TACAgent.TYPE_OUTFLIGHT;

import static se.sics.tac.aw.TACAgent.MIN_HOTEL;
import static se.sics.tac.aw.TACAgent.MIN_FLIGHT;
import static se.sics.tac.aw.TACAgent.MIN_ENTERTAINMENT;
import static se.sics.tac.aw.TACAgent.MAX_HOTEL;
import static se.sics.tac.aw.TACAgent.MAX_FLIGHT;
import static se.sics.tac.aw.TACAgent.MAX_ENTERTAINMENT;

import static se.sics.tac.aw.TACAgent.auctionCategoryToString;
import static se.sics.tac.aw.TACAgent.getAuctionCategory;
import static se.sics.tac.aw.TACAgent.getAuctionDay;
import static se.sics.tac.aw.TACAgent.getAuctionFor;
import static se.sics.tac.aw.TACAgent.getAuctionType;
import static se.sics.tac.aw.TACAgent.getAuctionTypeAsString;

import se.sics.tac.util.ArgEnumerator;

import static java.lang.Math.abs;
import static java.lang.Math.min;
import static java.lang.Math.max;
import static java.lang.Math.round;
import java.util.Vector;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.logging.Logger;

/**
 * TAC classic agent Snorky
 * 
 * @author Muddasser Alam (Hotels)
 * @author Paul Broadhurst (Flights)
 * @author Zoe Slattery (Entertainment)
 * 
 * @version 04:00 14/04/2008
 */
public class AgentSnorky extends AgentImpl {

	private static final Logger log = Logger.getLogger(AgentSnorky.class.getName());
	
	private ClientAllocation[] clientAllocations;	// index is equal to clientID [0,7]
	private FlightAuction[] flightAuctions; 		// index is equal to auctionID [0,7]
	private TicketAllocator ta;						// Entertainment ticket allocator
	HotelBidding hb = new HotelBidding();
	boolean flightCA = false;
	int[] offsets;
	float[] hAsk = new float[8];
	/* TAC AgentWare event triggered methods */
	/**
	 * Called once before the first game starts.
	 * Create objects that persist over multiple games here.
	 */
	protected void init(ArgEnumerator args) {
		log.fine("init() called");

		flightAuctions = new FlightAuction[8];
		clientAllocations = new ClientAllocation[8];
	}

	/**
	 * Called at the start of every game.
	 * Create objects that persist for exactly one game here.
	 * Also used for first reaction to client preferences.
	 */
	public void gameStarted() {
		log.fine("gameStarted() called");
		
		offsets = new int[8];

		// create flight auction objects
		for (int auctionID = 0; auctionID < 8; auctionID++)
			flightAuctions[auctionID] = new FlightAuction(auctionID);

		// create entertainment object
		ta = new TicketAllocator(agent, log);
		ta.setSortedTicketPrices();	

		// create client allocations and make initial allocation
		for (int clientID = 0; clientID < 8; clientID++)
			clientAllocations[clientID] = new ClientAllocation(clientID);
		ta.findBestTicketDistribution(inFlights(), outFlights());
//		clientAllocator();
	}

	/**
	 * Called when a single quote is received.
	 * Used to maintain flight & hotel auction tracking objects and trigger entertainment bidding.
	 */
	public void quoteUpdated(Quote quote) {
		log.fine("quoteUpdated(" + getAuctionTypeAsString(quote.getAuction())
				+ ", " + quote.getAuctionStatusAsString() + ", $" + quote.getBidPrice() +
				", $" + quote.getAskPrice() + ") called");

		int auction = quote.getAuction();
		int auctionCategory = getAuctionCategory(auction);

		if (auctionCategory == CAT_FLIGHT) {
			// update flight auction object and send new bid
			FlightAuction flightAuction = getFlightAuction(quote.getAuction());
			int time = (int) ((quote.getLastQuoteTime()- agent.getStartTime()) / 1000);
			assert time >= 0 && time <= 540 : "Time expected to be in range [0,540]";

			try {
				flightAuction.updatePrice(time, round(quote.getAskPrice()));
				if (flightAuction.seedCountDecreased())
					flightCA = true;
			} catch (FlightException e) {
				log.warning("Flight exception: " + e.getMessage());
			}

		} else if (auctionCategory == CAT_ENTERTAINMENT){
			float[] bidPrices = ta.getBidPrices(auction);
			if (bidPrices.length>0){
				float bidPrice = getEntBidPrice(bidPrices[0]);
				if ( (bidPrices[0]>0 && quote.getAskPrice()<bidPrice)
						|| (bidPrices[0]<0 && quote.getBidPrice()>bidPrice) )
					updateEntertainmentBid(auction);
			}
		}
	}

	/**
	 * Called when an auction closes.
	 * Mainly used for hotel tracker maintenance, reallocation, and bidding.
	 * May be used for entertainment.
	 */
	public void auctionClosed(int auction) {
		log.fine("auctionClosed(" + getAuctionTypeAsString(auction) + ") called");
		if (getAuctionCategory(auction) == CAT_HOTEL){
			if (agent.getAllocation(auction) > agent.getOwn(auction))
				recoverFromMissedHotel(auction);
			if (agent.getAllocation(auction) > agent.getOwn(auction))
				log.warning("recovery failed");
			
			clientAllocator();
		}
	}

	/**
	 * Called when a batch of quotes from the same auction category have been
	 * received and processed. Trigger for reallocation and bidding layers.
	 */
	public void quoteUpdated(int auctionCategory) {
		log.fine("quoteUpdated(" + auctionCategoryToString(auctionCategory) + ") called");

		if (auctionCategory == CAT_FLIGHT){
			long time = agent.getGameTime();
			long minuteTime = time % 60000;
			log.fine("CAT_FLIGHT, minuteTime = " + minuteTime);
			if (minuteTime >= 10000 || time < 10000) {
				if (flightCA || time < 10000)
					clientAllocator();
				else{
					updateFlightBids();
					updateEntertainmentBids();
				}
			}

			flightCA = false;
			
		}	
		
	}

	/**
	 * Called when a bid is successfully updated.
	 * Currently used by bidding layer only but may be used by hotel auction tracker.
	 */
	public void bidUpdated(Bid bid) {
		log.fine("bidUpdated(" + bid.getID() + ", "
				+ getAuctionTypeAsString(bid.getAuction()) + ", "
				+ bid.getProcessingStateAsString() + ") called");
	}

	/**
	 * Called when a bid is rejected.
	 * Currently used by bidding layer only but may be used by hotel auction tracker.
	 */
	public void bidRejected(Bid bid) {
		log.warning("bidRejected(" + bid.getID() + ", "
				+ getAuctionTypeAsString(bid.getAuction()) + ", "
				+ bid.getRejectReasonAsString() + ") called");
		
		int auctionCategory = getAuctionCategory(bid.getAuction());
		int bidReason = bid.getRejectReason();

		if (auctionCategory == CAT_HOTEL){
			if (bidReason == Bid.PRICE_NOT_BEAT && offsets[bid.getAuction()-MIN_HOTEL] < 8 - getHotelMinute()) {
				offsets[bid.getAuction()-MIN_HOTEL]++;
				updateHotelBid(bid.getAuction());
			}
		}

	}

	/**
	 * Called when a bid error occurs.
	 * Currently used by bidding layer only but may be used by hotel auction tracker.
	 */
	public void bidError(Bid bid, int status) {
		log.warning("bidError(" + bid.getID() + ", "
				+ getAuctionTypeAsString(bid.getAuction()) + ", "
				+ agent.commandStatusToString(status) + ") called");
	}
	
	/**
	 * Called when a transaction has occurred.
	 * Currently used to reallocate tickets.
	 */
	public void transaction(Transaction transaction) {
		log.fine("transaction(" 
				+ getAuctionTypeAsString(transaction.getAuction()) + ", "
				+ transaction.getQuantity() + " unit(s) at $" + transaction.getPrice()
				+ ") called");
		
		int auctionCategory = getAuctionCategory(transaction.getAuction());

		if (auctionCategory == CAT_ENTERTAINMENT){
			setAuctionAllocations();
			updateEntertainmentBids();
			
		}
	}
	

	
	/**
	 * Called at the end of every game.
	 * Destroy objects that persist for exactly one game here.
	 */
	public void gameStopped() {
		log.fine("gameStopped() called");

		displayGameResults();
	    
		for (int i = 0; i < 8; i++) {
			flightAuctions[i] = null;
			clientAllocations[i] = null;
		}

		ta = null;

	    
	    for (int auction=MIN_HOTEL; auction<=MAX_HOTEL; auction++)
	    	hAsk[auction-MIN_HOTEL] = agent.getQuote(auction).getAskPrice();
	    hb.insertNewAvg(hAsk);
	    
	    
	}
	
	/** Prints the estimated game results to the log */
	private void displayGameResults(){
		log.fine("Estimated game results:");
		NumberFormat myFormat = NumberFormat.getInstance();
		myFormat.setMaximumFractionDigits(2);
		myFormat.setGroupingUsed(false);
		
	    int utility = 0;
	    float cost = 0f;
	    float score = 0f;
	    for (ClientAllocation ca : clientAllocations) {
	    	log.fine("\tClient " + (ca.clientID+1) + ":\t" + ca.getInDay() + "\t" + ca.getOutDay()
	    			+ "\t" + (ca.tampaTowers ? "Tampa Towers\t" : "Shoreline Shanties")
	    			+ "\t" + (ca.ent1>-1 ? getAuctionTypeAsString(ca.ent1) : "\t\t")
	    			+ "\t" + ca.getUtility() + "\t" + myFormat.format(ca.getCost())
	    			+ "\t" +  myFormat.format(ca.getScore()));
	    	if (ca.ent2>-1)
	    		log.fine("\t\t\t\t\t\t\t\t" + getAuctionTypeAsString(ca.ent2));
	    	if (ca.ent3>-1)
	    		log.fine("\t\t\t\t\t\t\t\t" + getAuctionTypeAsString(ca.ent3));
	    	
	    	utility += ca.getUtility();
	    	cost += ca.getCost();
	    	score += ca.getScore();
	    }
	    log.fine("\tSum:\t\t\t\t\t\t\t\t\t\t" + utility + "\t" + myFormat.format(cost)
	    		+ "\t" + myFormat.format(score));
	    
	    float realCost = 0f;
	    for (int auction=0; auction<28; auction++)
	    	realCost += agent.getCost(auction);
	    log.fine("\tOther cost:\t\t\t\t\t\t\t\t\t\t" + myFormat.format((realCost-cost)));
	    
	    log.fine("\tTotal:\t\t\t\t\t\t\t\t\t\t" + utility + "\t" + myFormat.format(realCost) +
	    		"\t" + myFormat.format((utility-realCost)));
	}

	/* end of TAC AgentWare event triggered methods */

	
	/* auction tracking */
	/** Returns the {@link FlightAuction} object corresponding to an auction ID. */
	private FlightAuction getFlightAuction(int auctionID) {
		assert getAuctionCategory(auctionID) == CAT_FLIGHT : "Flight auction ID expected";
		return flightAuctions[auctionID];
	}

	/* end of auction tracking */

	
	/* bidding */
	/** Creates and sends a new bid with a single bid point */
	private void addBid(int auctionID, int quantity, float price) {
		addBid(auctionID, new int[] {quantity}, new float[] {price});
	}
	
	/** Creates and sends a new bid with multiple bid points */
	private void addBid(int auctionID, int[] quantities, float[] prices) {
		Bid newBid = new Bid(auctionID);
		
		for (int i=0, length = min(quantities.length, prices.length); i< length; i++) 
			newBid.addBidPoint(quantities[i], prices[i]);
		
		
		Bid oldBid = agent.getBid(auctionID);
		if (oldBid != null && (oldBid.getNoBidPoints() == quantities.length
				|| (oldBid.getNoBidPoints() == quantities.length+1))) {
			
			int offset = (oldBid.getNoBidPoints() == quantities.length) ? 0 : 1;
			boolean sameBid = true;
			for (int i=0; i< quantities.length; i++)
				if (((int) oldBid.getPrice(i+offset) != (int) prices[i])
						|| (oldBid.getQuantity(i+offset) != quantities[i])) {
					sameBid = false;
					break;
				}
			if (sameBid)	return;
			
		}
		
		oldBid = agent.getBid(auctionID);
		if (oldBid != null && oldBid.isPreliminary()) {
			log.warning("bid not submitted - old bid was preliminary: "
					+ getAuctionTypeAsString(auctionID)
					+ ", " + newBid.getBidString());
			return;
		}
			
			
		
		
		if (oldBid == null)
			agent.submitBid(newBid);
		else
			agent.replaceBid(oldBid, newBid);
		
		
		
		log.fine("Submitting " +
				(oldBid == null || oldBid.isPreliminary() ? "new" : "replacement") +
				" bid on " + getAuctionTypeAsString(auctionID)
				+ ": " + newBid.getBidString());
		
		Bid storedBid = agent.getBid(auctionID);
		if (newBid.getBidString() != storedBid.getBidString())
			log.warning("Bid error: stored bid string is not equal to submitted bid sting "
					+ storedBid.getBidString());
			
		
	}
	
	/** Updates the bids in all auctions. Called at end of clientAllocator. */
	private void updateAllBids(){
		log.fine("updateAllBids() called");

		updateEntertainmentBids();
		updateFlightBids();
		updateHotelBids();
	}

	private void updateEntertainmentBids(){
		log.fine("updateEntertainmentBids() called");
		
		for (int auction=MIN_ENTERTAINMENT; auction<=MAX_ENTERTAINMENT; auction++)
			updateEntertainmentBid(auction);
	}

	private void updateEntertainmentBid(int auction){
		float[] bidPrices = ta.getBidPrices(auction);
		
		int quantity = bidPrices.length;
		
		if (quantity == 0) {
			if (agent.getBid(auction) != null && agent.getBid(auction).getNoBidPoints() > 0)
				addBid(auction, 0, 0);
			
		} else if (bidPrices[0] > 0) {	// buying units
			bidPrices[0] = getEntBidPrice(bidPrices[0]);
			addBid(auction, 1, bidPrices[0]);
			
		} else if (bidPrices[0] < 0){	// selling units
			for (int i=0; i<quantity; i++)
				bidPrices[i] = getEntBidPrice(bidPrices[i]);
			int[] quantities = new int[quantity];
			Arrays.fill(quantities, -1);
			addBid(auction, quantities, bidPrices);
		}
	}
	
	/** Returns adjusted entertainment bid price */
	private float getEntBidPrice(float bidPrice){
		if (bidPrice>0) {
			float upperLimit = bidPrice * 0.75f;
			float bid  = 50 + ((agent.getGameTime() * upperLimit) / 720000);
			if (bid > upperLimit) return (int) upperLimit;
			return (int) bid;
		}	

		else if (bidPrice<0){
			float margin = 10.0f;
			float limitPrice = Math.max(75, -bidPrice);
			float bid = limitPrice * 1.75f - (agent.getGameTime() * limitPrice) / 720000;
			
			if (bid < limitPrice) {
				if (-bidPrice > 1) return limitPrice + margin; // Not an endowed ticket, don't sell for less that utility + margin
				return limitPrice; // Endowed. Don't sell for less than 75.
			}else{
				return bid;
			}		
		} else
			return 0;
	}
	
	private void updateFlightBids(){
		log.fine("updateFlightBids() called");
		
		for (int auctionID=MIN_FLIGHT; auctionID<=MAX_FLIGHT; auctionID++)
			updateFlightBid(auctionID);
	}
	
	private void updateFlightBid(int auctionID){
		FlightAuction fa = getFlightAuction(auctionID);
		int unitsOwned = agent.getOwn(auctionID);
		int unitsNeeded = agent.getAllocation(auctionID);

		try {
			if (unitsOwned < unitsNeeded && (fa.buyNow(agent.getGameTime()) || agent.getGameTimeLeft() <= 10000))
				addBid(auctionID, unitsNeeded - unitsOwned, fa.getCurrentPrice());
			else if (unitsOwned < unitsNeeded / 2 && fa.seedIncreasing() > 0.8)
				addBid(auctionID, unitsNeeded / 2 - unitsOwned, fa.getCurrentPrice());
		} catch (FlightException e) {
			log.warning("Flight exception: " + e.getMessage());
		}
	}

	private void updateHotelBids(){
		log.fine("updateHotelBids() called");
		
		for (int auction=MIN_HOTEL; auction<=MAX_HOTEL; auction++)
			updateHotelBid(auction);
	}
	
	private void updateHotelBid(int auction){
		Quote quote = agent.getQuote(auction);
		if (quote.isAuctionClosed())
			return;
		
		Vector<Float> marginalValues = new Vector<Float>();
		for (ClientAllocation ca : clientAllocations)
			if (auction >= ca.getInHotel() && auction <= ca.getOutHotel())
				marginalValues.add(ca.getHotelLimit());
			
		

		marginalValues = InsertionSort.sortDescending(marginalValues);
		
		int minute = Math.min(getHotelMinute() + offsets[auction-MIN_HOTEL],8);
		
		if (marginalValues.size() > 0 || (marginalValues.size() == 0 && minute==1) || quote.getHQW()>0) {
			log.fine("Requesting bid prices for " + getAuctionTypeAsString(auction)
					+ " with marginal values " + marginalValues.toString()
					+ " and asking price $" + quote.getAskPrice()
					+ " at minute " + minute);
			//ALAM:2/5 Change
			//If the corresponding auction is closed we bid comparatively more to 
			//avoid not getting the ticket even we have a good marginal Value for it.
			
			int counterAuction = (TACAgent.getAuctionType(auction)==TACAgent.TYPE_CHEAP_HOTEL?(auction+4):(auction-4));
			boolean counterAuctionClosed = agent.getQuote(counterAuction).isAuctionClosed();
			
			log.fine("**********   COUNTER AUCTION: "+counterAuction+ " is "+(counterAuctionClosed?"CLOSED":"OPEN"));		
			
			float[] bidPrices = hb.getBidPrices(auction, marginalValues, quote.getAskPrice(), minute, quote.getHQW(),counterAuctionClosed) ;
			if(bidPrices.length>0)
			{
				int[] quantities = new int[bidPrices.length];
			
				Arrays.fill(quantities, 1);
				addBid(auction, quantities, bidPrices);
			}
			else
				
				log.fine("*************** NO BID PRICES RECEIVED FOR AUCTION: "+ auction + " at minute:  "+minute);
		}
	}
	
	private int getHotelMinute(){
		return (int) Math.ceil( (float)agent.getGameTime() / 60000);
	}
	/* end of bidding */
	
	
	/* allocation */
	/** Nested class to store planned client unit allocations */
	class ClientAllocation {
		public int clientID;

		// auction IDs or -1 if not assigned
		public int inFlight;
		public int outFlight;
		public int ent1;
		public int ent2;
		public int ent3;
		public boolean tampaTowers;

		ClientAllocation(int newClientID) {
			clientID = newClientID;

			inFlight = getAuctionFor(CAT_FLIGHT, TYPE_INFLIGHT,
					agent.getClientPreference(clientID, ARRIVAL));
			outFlight = getAuctionFor(CAT_FLIGHT, TYPE_OUTFLIGHT, 
					agent.getClientPreference(clientID, DEPARTURE));
			
			

				
			tampaTowers = (agent.getClientPreference(clientID, HOTEL_VALUE) / getStayLength() >
					(hb.getHotelPriceDiff(agent.getClientPreference(clientID, ARRIVAL),
					agent.getClientPreference(clientID, DEPARTURE))));
			ent1 = -1;
			ent2 = -1;
			ent3 = -1;
		}
		
		ClientAllocation(ClientAllocation ca) {
			clientID = ca.clientID;
			inFlight = ca.inFlight;
			outFlight = ca.outFlight;
			tampaTowers = ca.tampaTowers;
			ent1 = ca.ent1;
			ent2 = ca.ent2;
			ent3 = ca.ent3;
		}
		
		public int getInDay()	{return (inFlight > -1) ? getAuctionDay(inFlight) : -1;}
		public int getOutDay()	{return (outFlight > -1) ? getAuctionDay(outFlight) : -1;}
		
		/** Returns the number of hotel days spent at the resort */
		public int getStayLength()	{return getOutDay() - getInDay();}

		/** Returns the hotel auction ID for the first day */
		public int getInHotel() {
			if (inFlight > -1)
				return getAuctionFor(CAT_HOTEL, (tampaTowers) ?
					TYPE_GOOD_HOTEL : TYPE_CHEAP_HOTEL, getInDay());
			else
				return -1;
		}

		/** Returns the hotel auction ID for the last day */
		public int getOutHotel() {
			if (outFlight > -1)
				return getAuctionFor(CAT_HOTEL, (tampaTowers) ?
					TYPE_GOOD_HOTEL : TYPE_CHEAP_HOTEL, getOutDay() - 1);
			else
				return -1;
		}

		public int getEnt1Day() {return (ent1 > -1) ? getAuctionDay(ent1) : -1;}
		public int getEnt2Day() {return (ent2 > -1) ? getAuctionDay(ent2) : -1;}
		public int getEnt3Day() {return (ent3 > -1) ? getAuctionDay(ent3) : -1;}
	
		public float getFlightCost() {
			if (!isViable())
				return 0f;
			
			float cost = 0f;
			
			for (int auction = inFlight; auction <= outFlight; auction += outFlight - inFlight)
			{
				float flightCost = agent.getCost(auction);
				int unitsNeeded = agent.getAllocation(auction) - agent.getOwn(auction);
				if (unitsNeeded>0)
					flightCost += getFlightAuction(auction).getBuyPrice() * unitsNeeded;
				
				flightCost /= agent.getAllocation(auction);
				cost += flightCost;
			}
			
			
			return cost;
			
		}
		
		public float getHotelCost() {
			if (!isViable())
				return 0f;
			
			float cost = 0f;
			
			for (int auction = getInHotel(); auction <= getOutHotel(); auction++){
				if (agent.getOwn(auction)>0)
					cost += agent.getCost(auction) / agent.getOwn(auction);
				else
					cost += Math.max(hb.getAvgValueOfHotel(auction-MIN_HOTEL),
							agent.getQuote(auction).getAskPrice());
			}
			
			return cost;
		}

		public float getEntertainmentCost() {
			if (!isViable())
				return 0f;
			
			float cost = 0f;
			
			if (ent1>-1 && agent.getCost(ent1) > 0f && agent.getOwn(ent1)>0)
				cost += agent.getCost(ent1) / agent.getOwn(ent1);
			if (ent2>-1 && agent.getCost(ent2) > 0f && agent.getOwn(ent2)>0)
				cost += agent.getCost(ent2) / agent.getOwn(ent2);
			if (ent3>-1 && agent.getCost(ent3) > 0f && agent.getOwn(ent3)>0)
				cost += agent.getCost(ent3) / agent.getOwn(ent3);
			
			return cost;
			
		}
		
		public float getCost() {
			return getFlightCost() + getHotelCost() + getEntertainmentCost();
		}
		
		/** utility = 1000 - travel penalty + hotel bonus + entertainment bonus */
		public int getUtility() {
			if (!isViable())
				return 0;
			
			int utility = 1000;
			
			utility -= 100 * abs(agent.getClientPreference(clientID, ARRIVAL) - getInDay());
			utility -= 100 * abs(agent.getClientPreference(clientID, DEPARTURE) - getOutDay());
			
			if (tampaTowers)	utility += agent.getClientPreference(clientID, HOTEL_VALUE);
			
			if (ent1>-1)	utility += agent.getClientPreference(clientID, E1);
			if (ent2>-1)	utility += agent.getClientPreference(clientID, E2);
			if (ent3>-1)	utility += agent.getClientPreference(clientID, E3);
			
			return utility;
		}
		
		public float getScore() {
			if (!isViable())
				return 0f;
			
			return getUtility() - getCost();
		}
		
		public float getHotelLimit() {
			if (!isViable())
				return 0f;
			
			float cost = 0f;
			
			for (int auction = inFlight; auction <= outFlight; auction += outFlight - inFlight)
			{
				float flightCost = 0f;
				int unitsNeeded = agent.getAllocation(auction) - agent.getOwn(auction);
				if (unitsNeeded>0) {
					flightCost += getFlightAuction(auction).getBuyPrice() * unitsNeeded;
					flightCost /= unitsNeeded;
					cost += flightCost;
				}
			}
			
			int hotelCount = 0;
			for (int auction = getInHotel(); auction <= getOutHotel(); auction++){
				if (!agent.getQuote(auction).isAuctionClosed())
					hotelCount++;
			}
			
			if (hotelCount>0)
				return (getUtility() - cost) / hotelCount;
			else
				return 0f;
			
		}
		
		public void dropClient() {
			inFlight = -1;
			outFlight = -1;
			ent1 = -1;
			ent2 = -1;
			ent3 = -1;
		}
		
		/** Check if client has bee dropped */
		public boolean isViable() {
			boolean viable = true;
			viable = viable && inFlight>=0 && inFlight<=3;
			viable = viable && outFlight>=4 && outFlight<=7;
			viable = viable && getStayLength()>=1;
			
			return viable;
		}
		
		/** Checks that a new configuration is possible given current allocation/auction status */
		public boolean isPossible(ClientAllocation newCA) {
			if (!newCA.isViable())	return false;
			
			for (int auction=newCA.getInHotel(); auction<=newCA.getOutHotel(); auction++) {
				boolean alreadyAllocated = getInHotel() >= auction && getOutHotel() <= auction;
				boolean canAllocate = !(agent.getQuote(auction).isAuctionClosed()
					&& agent.getAllocation(auction) >= agent.getOwn(auction));
					
				if (!alreadyAllocated && !canAllocate)
					return false;
			}
			
			return true;
		}
	}

	/** Adjusts the current client allocations to a more efficient configuration. */
	private void clientAllocator() {
		log.fine("clientAllocator() called");
		
		adjustFlightDays();
		setAuctionAllocations();
		logClientAllocations();
		//logAuctionAllocations();
		updateAllBids();
	}
	
	/** Assigns entertainment tickets to clients */
	private void updateClientEntertainmentAllocation(ClientAllocation[] cas, TicketAllocator ta){
		ta.findBestTicketDistribution(inFlights(cas),outFlights(cas));
		
		// update client allocation objects
		for (ClientAllocation ca : cas) {
			ca.ent1 = ta.getAuctionID(ca.clientID, 1);
			ca.ent2 = ta.getAuctionID(ca.clientID, 2);
			ca.ent3 = ta.getAuctionID(ca.clientID, 3);
		}
	}
	
	/** Adjusts client flight days if profitable */
	private void adjustFlightDays() {
		adjustFlightDays(clientAllocations, ta);
	}
	
	private void adjustFlightDays(ClientAllocation[] cas, TicketAllocator ta) {
		
		TicketAllocator localTA = new TicketAllocator(agent, log);
		ClientAllocation[] localCAs = new ClientAllocation[8];
		for (int client=0; client<8; client++)
			localCAs[client] = new ClientAllocation(cas[client]);
		
		setAuctionAllocations(cas, ta);
		float currentScore = getScore(cas);
		log.fine("Current score: " + currentScore);
		
		for (int clientID=0; clientID<8; clientID++) {
			
			if (!cas[clientID].isViable())	continue;
			
			boolean loop;
			
			do {
				loop = false;
				
				ClientAllocation ca = cas[clientID];
				ClientAllocation lca = localCAs[clientID];
			
				float newScore = -100000;
				int newConfig = 0;
				
				// arrive early
				lca.inFlight--;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " arrive early: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 1;
					}
				}
				lca.inFlight++;
				
				// arrive late
				lca.inFlight++;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " arrive late: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 2;
					}
				}
				lca.inFlight--;
				
				// leave late
				lca.outFlight++;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " leave late: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 3;
					}
				}
				lca.outFlight--;

				// leave early
				lca.outFlight--;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " leave early: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 4;
					}
				}
				lca.outFlight++;
				
				// switch hotel
				lca.tampaTowers = !ca.tampaTowers;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, ta);
					
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " switch hotel: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 5;
					}
				}
				
				// arrive early
				lca.inFlight--;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " switch & arrive early: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 6;
					}
				}
				lca.inFlight++;
				
				// arrive late
				lca.inFlight++;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " switch & arrive late: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 7;
					}
				}
				lca.inFlight--;
				
				// leave late
				lca.outFlight++;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " switch & leave late: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 8;
					}
				}
				lca.outFlight--;

				// leave early
				lca.outFlight--;
				setAuctionAllocations(cas, ta);
				if (ca.isPossible(lca)) {
					setAuctionAllocations(localCAs, localTA);
					
					float tempScore = getScore(localCAs);
					log.fine("C" + (ca.clientID+1) + " switch & leave early: " + tempScore);
					if (tempScore > newScore){
						newScore = tempScore;
						newConfig = 9;
					}
				}
				lca.outFlight++;
				
				lca.tampaTowers = ca.tampaTowers;
				
				float diff = 20f + 30f * (1f - (float)agent.getGameTime() / 720000);
				if (currentScore + diff < newScore){
					loop = true;
						
					switch (newConfig)
					{
					case 1:	ca.inFlight--;
							lca.inFlight--;
							log.fine("Client " + (ca.clientID+1) + " arriving early");
							break;
					case 2:	ca.inFlight++;
							lca.inFlight++;
							log.fine("Client " + (ca.clientID+1) + " arriving late");
							break;
					case 3:	ca.outFlight++;
							lca.outFlight++;
							log.fine("Client " + (ca.clientID+1) + " leaving late");
							break;
					case 4:	ca.outFlight--;
							lca.outFlight--;
							log.fine("Client " + (ca.clientID+1) + " leaving early");
							break;
					case 5:	ca.tampaTowers = !ca.tampaTowers;
							lca.tampaTowers = !lca.tampaTowers;
							log.fine("Client " + (ca.clientID+1) + " switching hotel");
							break;
					case 6:	ca.inFlight--;
							lca.inFlight--;
							ca.tampaTowers = !ca.tampaTowers;
							lca.tampaTowers = !lca.tampaTowers;
							log.fine("Client " + (ca.clientID+1) + " arriving early and switch hotel");
							break;
					case 7:	ca.inFlight++;
							lca.inFlight++;
							ca.tampaTowers = !ca.tampaTowers;
							lca.tampaTowers = !lca.tampaTowers;
							log.fine("Client " + (ca.clientID+1) + " arriving late and switch hotel");
							break;
					case 8:	ca.outFlight++;
							lca.outFlight++;
							ca.tampaTowers = !ca.tampaTowers;
							lca.tampaTowers = !lca.tampaTowers;
							log.fine("Client " + (ca.clientID+1) + " leaving late and switch hotel");
							break;
					case 9:	ca.outFlight--;
							lca.outFlight--;
							ca.tampaTowers = !ca.tampaTowers;
							lca.tampaTowers = !lca.tampaTowers;
							log.fine("Client " + (ca.clientID+1) + " leaving early and switch hotel");
							break;
					}
					currentScore = newScore;
				}
			} while(loop);
			
			setAuctionAllocations(cas, ta);
		}
	}
	
	private void recoverFromMissedHotel(int auction){
		java.awt.Toolkit.getDefaultToolkit().beep();
		log.warning("Did not win enough rooms for " + getAuctionTypeAsString(auction));
		
		int unitsNeeded = agent.getAllocation(auction) - agent.getOwn(auction);
		
		// prepare to generate scores
		TicketAllocator localTA = new TicketAllocator(agent, log);
		ClientAllocation[] localCAs = new ClientAllocation[8];
		for (int client=0; client<8; client++)
			localCAs[client] = new ClientAllocation(clientAllocations[client]);
		
		float[] newScore = new float[8];
		int[] newConfig = new int[8];
		
		// Hotel types
		int curType = getAuctionType(auction);
		int newType = curType == TYPE_GOOD_HOTEL ? TYPE_CHEAP_HOTEL : TYPE_GOOD_HOTEL;
		
		// find affected clients and drop them from local client allocation
		boolean[] affectedClients = new boolean[8];
		int affectedCount = 0;
		for (ClientAllocation ca : clientAllocations)
			if (auction >= ca.getInHotel() && auction <= ca.getOutHotel()) {
				affectedClients[ca.clientID] = true;
				localCAs[ca.clientID].dropClient();
				affectedCount++;
			}
		log.warning("Another " + unitsNeeded + " rooms needed, "
				+ affectedCount + " clients affected");
		
		
		do {
			
			// intialise scores / configurations
			Arrays.fill(newScore,-10000f);
			Arrays.fill(newConfig,-1);
		
			for (ClientAllocation ca : clientAllocations) {
				if (!affectedClients[ca.clientID])	continue;
				ClientAllocation lca = localCAs[ca.clientID];
				
				// keep client at current hotel if possible
				ClientAllocation newCA = new ClientAllocation(ca);
				setAuctionAllocations(localCAs, localTA);
				if (lca.isPossible(newCA)) {
					localCAs[ca.clientID] = new ClientAllocation(newCA);
					
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					if (tempScore>newScore[ca.clientID]) {
						newScore[ca.clientID] = tempScore;
						newConfig[ca.clientID] = 0;
					}
					
					localCAs[ca.clientID].dropClient();
				}
				
	
				// switch clients to other hotel if possible
				newCA.tampaTowers = !ca.tampaTowers;
				setAuctionAllocations(localCAs, localTA);
				if (lca.isPossible(newCA)) {
					localCAs[ca.clientID] = new ClientAllocation(newCA);
					
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					if (tempScore>newScore[ca.clientID]) {
						newScore[ca.clientID] = tempScore;
						newConfig[ca.clientID] = 1;
					}
					
					localCAs[ca.clientID].dropClient();
				}
				newCA.tampaTowers = ca.tampaTowers;
				
				
				// Leave early
				if (getAuctionDay(auction) > 1) {
					newCA.outFlight = getAuctionFor(CAT_FLIGHT, TYPE_OUTFLIGHT, getAuctionDay(auction));
					newCA.inFlight = min(lca.inFlight,
							getAuctionFor(CAT_FLIGHT, TYPE_INFLIGHT, getAuctionDay(auction)-1));
					
					setAuctionAllocations(localCAs, localTA);
					if (lca.isPossible(newCA)) {
						localCAs[ca.clientID] = new ClientAllocation(newCA);
						
						setAuctionAllocations(localCAs, localTA);
						float tempScore = getScore(localCAs);
						if (tempScore>newScore[ca.clientID]) {
							newScore[ca.clientID] = tempScore;
							newConfig[ca.clientID] = 2;
						}
						
						localCAs[ca.clientID].dropClient();
					}
					
					newCA.outFlight = ca.outFlight;
					newCA.inFlight = ca.inFlight;
				}
				
				
				// Arrive late
				if (getAuctionDay(auction) < 4) {
					newCA.inFlight = getAuctionFor(CAT_FLIGHT, TYPE_INFLIGHT, getAuctionDay(auction)+1);
					newCA.outFlight = max(lca.outFlight,
							getAuctionFor(CAT_FLIGHT, TYPE_OUTFLIGHT, getAuctionDay(auction)+2));
					
					setAuctionAllocations(localCAs, localTA);
					if (lca.isPossible(newCA)) {
						localCAs[ca.clientID] = new ClientAllocation(newCA);
						
						setAuctionAllocations(localCAs, localTA);
						float tempScore = getScore(localCAs);
						if (tempScore>newScore[ca.clientID]) {
							newScore[ca.clientID] = tempScore;
							newConfig[ca.clientID] = 3;
						}
						
						localCAs[ca.clientID].dropClient();
					}
					
					newCA.outFlight = ca.outFlight;
					newCA.inFlight = ca.inFlight;
				}
				
				// Drop client
				{
					setAuctionAllocations(localCAs, localTA);
					float tempScore = getScore(localCAs);
					if (tempScore>newScore[ca.clientID]) {
						newScore[ca.clientID] = tempScore;
						newConfig[ca.clientID] = 4;
					}
				}
			}
			
			// Find best configuration
			int choice = findMaxValue(newScore);
			
			// Update client allocations
			ClientAllocation ca = clientAllocations[choice];
			
			switch(newConfig[choice]) {
			case 0:
				log.warning("Keeping client " + (choice+1) + " at "
						+ (curType==TYPE_GOOD_HOTEL ? "tampa towers" : "shoreline shanties"));
				break;
			
			case 1:
				ca.tampaTowers = !ca.tampaTowers;
				log.warning("Client " + (choice+1) + " switched to "
						+ (newType==TYPE_GOOD_HOTEL ? "tampa towers" : "shoreline shanties"));
				break;
			case 2:
				ca.outFlight = getAuctionFor(CAT_FLIGHT, TYPE_OUTFLIGHT, getAuctionDay(auction));
				ca.inFlight = min(ca.inFlight,
						getAuctionFor(CAT_FLIGHT, TYPE_INFLIGHT, getAuctionDay(auction)-1));
				log.warning("Client " + (choice+1) + " leaving early (day " + ca.getOutDay() + ")");
				break;
			case 3:
				ca.inFlight = getAuctionFor(CAT_FLIGHT, TYPE_INFLIGHT, getAuctionDay(auction)+1);
				ca.outFlight = max(ca.outFlight,
						getAuctionFor(CAT_FLIGHT, TYPE_OUTFLIGHT, getAuctionDay(auction)+2));
				log.warning("Client " + (choice+1) + " arriving late (day " + ca.getInDay() + ")");
				break;
			case 4:
				ca.dropClient();
				log.warning("\tDropping client " + (choice+1));
				break;
			}
			
			affectedCount--;
			affectedClients[choice] = false;
			localCAs[choice] = new ClientAllocation(clientAllocations[choice]);
			
			setAuctionAllocations(localCAs, localTA);
		} while (affectedCount>0);

		
		setAuctionAllocations();
	}
	
	private int findMaxValue(float[] values) {
		int maxIndex = 0;
		
		for (int i=1; i<values.length; i++)
			if (values[i] > values[maxIndex])
				maxIndex = i;
		
		return maxIndex;
	}
	
	/** Updates the auction allocations in agent */
	private void setAuctionAllocations(){
		setAuctionAllocations(clientAllocations, ta);
	}
	
	
	private void setAuctionAllocations(ClientAllocation[] cas, TicketAllocator ta){
		agent.clearAllocation();
		
		for (ClientAllocation client : cas) {
			incAllocation(client.inFlight);
			incAllocation(client.outFlight);

			for (int auctionID = client.getInHotel();
					auctionID <= client.getOutHotel(); auctionID++)
				incAllocation(auctionID);

			
			incAllocation(client.ent1);
			incAllocation(client.ent2);
			incAllocation(client.ent3);
		}
		
		updateClientEntertainmentAllocation(cas, ta);
		
		// set entertainment tickets to buy by incrementing agent allocation
		for (int auctionID=MIN_ENTERTAINMENT; auctionID<=MAX_ENTERTAINMENT; auctionID++)
			if (ta.getTicketsToAllocate(auctionID)<0)
				incAllocation(auctionID);
	}
	
	
	
	/** Prints the current client allocations to the log */
	@SuppressWarnings("unused")
	private void logClientAllocations() {
		logClientAllocations(clientAllocations);
	}
	
	private void logClientAllocations(ClientAllocation[] cas) {
		for (ClientAllocation client: cas)
			logClientAllocation(client);
	}
	
	private void logClientAllocation(ClientAllocation client) {
		String returnString = "Client " + (client.clientID+1) + ": ";
		
		if (client.isViable()) {
			returnString += "days " + client.getInDay() + " to " + client.getOutDay();
			returnString += " in " + (client.tampaTowers ? "Tampa Towers" : "Shoreline Shanties");
	
			String entString = "";
			if (client.ent1 > 0)
				entString += getAuctionTypeAsString(client.ent1) + ", ";
			if (client.ent2 > 0)
				entString += getAuctionTypeAsString(client.ent2) + ", ";
			if (client.ent3 > 0)
				entString += getAuctionTypeAsString(client.ent3) + ", ";
			
			if (entString.length() > 0)
				returnString += ", attending " + entString;
			else
				returnString += ", ";
			
			returnString += "score " + client.getScore();
		} else
			returnString += "dropped";
		
		log.fine(returnString);
	}
	
	
	/** Prints the current auction allocations to the log */
	@SuppressWarnings("unused")
	private void logAuctionAllocations() {
		log.fine("Agent allocations:");
		for (int auctionID = 0; auctionID < 28; auctionID++)
			log.fine(getAuctionTypeAsString(auctionID) + 
					" - owned " + agent.getOwn(auctionID) +
					", allocated " + agent.getAllocation(auctionID));
	}
	

	/** Increments the auction allocation in the agent object. */
	private void incAllocation(int auctionID) {
		if (auctionID >= 0 && auctionID < 28)
			agent.setAllocation(auctionID, agent.getAllocation(auctionID) + 1);
	}

	/** Returns an array of client in flight auction IDs. */
	private int[] inFlights(){
		return inFlights(clientAllocations);
	}
	
	private int[] inFlights(ClientAllocation[] cas){
		int[] inFlightDays = new int[8];
		for (ClientAllocation ca : cas)
			inFlightDays[ca.clientID] = ca.getInDay();
		return inFlightDays;
	}
	
	/** Returns an array of client out flight auction IDs. */
	private int[] outFlights(){
		return outFlights(clientAllocations);
	}

	private int[] outFlights(ClientAllocation[] cas){
		int[] outFlightDays = new int[8];
		for (ClientAllocation ca : cas)
			outFlightDays[ca.clientID] = ca.getOutDay();
		return outFlightDays;
	}
	
	/** Returns the estimated score for the game */
	private float getScore(ClientAllocation[] cas){
		float score = 0f;
		
		for (ClientAllocation ca : cas)
			score += ca.getUtility();
		
		for (int auction = 0; auction < 28; auction++)
			score -= agent.getCost(auction);
		
		for (int auction = MIN_FLIGHT; auction <= MAX_FLIGHT; auction++)
			score -= flightAuctions[auction].getBuyPrice()
				* max(agent.getAllocation(auction) - agent.getOwn(auction),0);
		
		for (int auction = MIN_HOTEL; auction <= MAX_HOTEL; auction++)
			score -= agent.getQuote(auction).getAskPrice()
				* max(agent.getAllocation(auction) - agent.getOwn(auction),0);
		
		return score;
	}
	
	private float getScore(){
		return getScore(clientAllocations);
	}
	
	/* end of allocation */

	// -------------------------------------------------------------------
	// Only for backward compatibility
	// -------------------------------------------------------------------
	public static void main(String[] args) {
		TACAgent.main(args);
	}

}